//是否作为DOM Properties 来设置属性 
//  如：el.checked = true  
// 而不是el.setAttribute('checked', true)
function shouldSetAsProps (el, key, value) {
  // 特殊处理， 遇到则补全
  if (key === 'form' && el.tagName === 'INPUT') {
    return false;
  }

  return key in el;
}


// 创建渲染器
function createRenderer (options) {

  //通过options 拿到对应操作的api
  const {
    createElement,
    setElementText,
    insert,
    patchProps,
    createText,
    setText,
    createComment,
    setComment,

  } = options


  /**
   * @param {旧vnode} n1
   * @param {新vnode} n2
   * @param {容器} container
   */


  function patchChildren (n1, n2, container) {
    //如果新节点是字符串
    if (typeof n2.children === 'string') {
      // 如果旧节点是数组  则需要先卸载
      if (Array.isArray(n1.children)) {
        n1.children.forEach(child => {
          unmount(child);
        })
      }

      // 无论旧节点是 字符串或null直接设置文本覆盖即可，无需额外处理
      setElementText(container, n2.children)
    } else if (Array.isArray(n2.children)) {// 如果新节点有多个子节点


      // 旧节点的子节点是否为数组
      if (Array.isArray(n1.children)) {
        // diff算法处理。
        // 目前暂时用笨方法处理
        n1.children.forEach(child => {
          unmount(child);
        })

        n2.children.forEach(child => {
          patch(null, child, container);
        })
      } else {
        // 无论是旧节点的子节点是 字符串还是null,先清空容器 再遍历打补丁
        setElementText(container, '');
        n2.children.forEach(child => {
          patch(null, child, container);
        })
      }
    } else {
      if (Array.isArray(n1.children)) {
        n1.children.forEach(child => {
          unmount(child);
        })
      } else {
        setElementText(container, '');
      }
    }
  }

  /**
    * @param {旧vnode} n1
    * @param {新vnode} n2 
    * 
    */
  function patchElement (n1, n2) {
    // 复用旧的元素
    const el = n2.el = n1.el;
    const oldProps = n1.props;
    const newProps = n2.props;

    //1. 更新props 
    for (const key in newProps) {
      if (newProps[key] !== oldProps[key]) {
        patchProps(el, key, oldProps[key], newProps[key])
      }
    }
    for (const key in oldProps) {
      if (!(key in newProps)) {
        // 如果新的props 中不存在这个属性  则删除这个属性
        patchProps(el, key, oldProps[key], null)
      }
    }

    // 2. 更新children
    pathChildren(n1, n2, el)
  }



  // 卸载元素
  function unmount (vnode) {
    if(vnode.type === Fragment) {
      vnode.children.forEach(child => {
        unmount(child);
      })
      return;
    }

    const parente = vnodel.el.parentNode;
    if (parente) {
      parente.removeChild(vnode.el)
    }
  }


  // 挂载元素
  function mountElement (vnode, container) {
    // 创建type类型元素
    const el = vnode.el = createElement(vnode.type);

    // 处理子节点 如果是string  表示元素有文本节点
    if (typeof vnode.children === 'string') {
      // 设置文本节点
      setElementText(el, vnode.children);
    } else if (Array.isArray(vnode.children)) {
      vnode.children.forEach(child => {
        //因为目前只是 挂载阶段 所以 旧vnode 传null即可
        patch(null, child, el)
      });
    }

    // 如果存在属性
    if (vnode.props) {
      for (const key in vnode.props) {
        // 调用setAttribute 方法设置数据
        const value = vnode.props[key]

        // 调用patchProps 方法设置属性
        patchProps(el, key, null, value);
      }
    }


    // 将元素添加到容器中
    insert(el, container);
  }

  //文本节点的标识
  const Text = Symbol();
  //注释节点的标识
  const Comment = Symbol();
  //  片段节点的标识
  const Fragment = Symbol();

  /**
   * n1 @param {*} 旧vnode
   * n2 @param {*} 新vnode
   * container @param {*} 容器
   *  在这里编写渲染逻辑
   */
  function patch (n1, n2, container) {

    // 区分新旧节点是否同一类型
    if (n1 && n1.type !== n2.type) {
      // 不是同一类型  就直接卸载旧的 
      unmount(n1);
      n1 = null;
    }


    const { type } = n2

    // 如果是字符串则表明是普通标签
    if (typeof type === 'string') {
      // 如果旧vnode 不存在 就表示挂载
      if (!n1) {
        // 挂载
        mountElement(n2, container)
      } else {
        // 打补丁  暂时省略
      }
    } else if (type === Text) {
      // 如果是文本节点
      if (!n1) {
        const el = n2.el = createText(n2.children);
        insert(el, container);
      } else {
        const el = n2.el = n1.el;
        if (n2.children !== n1.children) {
          setText(el, n2.children);
        }
      }

    } else if (type === Comment) {
      // 如果是注释节点
      if (!n1) {
        const el = n2.el = createComment(n2.children);
        insert(el, container);
      } else {
        const el = n2.el = n1.el;
        if (n2.children !== n1.children) {
          setComment(el, n2.children);
        }
      }
    } else if (type === Fragment) {
      // 如果是片段节点
      if (!n1) {
        // 如果就节点不存在，则直接遍历挂载fragment的子节点
        n2.children.forEach(child => {
          patch(null, child, container)
        })
      } else {//如果是旧节点
        // 就只需要比较Fragment上的子节点即可，
        // 因为Fragment 没有真实的DOM  所以 直接复用旧节点的子节点
        patchChildren(n1, n2, container)
      }
    } else if (typeof type === 'object') {
      // 如果是对象 则它描述是组件

    } else if (typeof type === 'xxx') {
      // 其他类型 vnode  特殊处理
    }

  }



  function render (vnode, container) {
    if (vnode) {
      // 新 节点存在， 就将旧vnode 一起传给patch 函数 进行打补丁
      // container._vnode 表示旧vnode
      patch(container._vnode, vnode, container)
    } else {
      if (container._vnode) {
        // 只有旧vnode 存在，  没有新vnode   就是卸载操作
        // container.innerHTML = '';
        unmount(container._vnode);
      }
    }


    // 存vnode 作为下一个的旧vnode
    container._vnode = vnode;
  }

  // 服务端渲染内容时讲解
  function hydrate (vnode, container) {
  }

  return {
    render,
    hydrate
  }
}

const vnode = {
  type: 'div',
  props: {
    id: 'foo',
    class: normalizeClass({
      name1: '1',
    }),
    onClick: () => {
      alert('11');
    },
    onContentmenu: () => {
      alert('22');
    }
  },
  textContent: 'hello'
}


const renderer = createRenderer({
  createElement (tag) {
    return document.createElement(tag);
  },
  setElementText (el, text) {
    el.textContent = text;
  },
  insert (el, parent, anchor = null) {
    parent.insertBefore(el, anchor);
  },
  createText (text) {
    return document.createTextNode(text);
  },
  setText (el, text) {
    el.nodeValue = text;
  },

  creatComment (text) {
    return document.createComment(text);
  },
  setComment (el, text) {
    el.nodeValue = text;
  },

  patchProps (el, key, prevValue, nextValue) {
    if (/^on/.test(key)) {
      // 定义 el._vei 为一个对象，存在事件名称到事件处理函数的映射
      const invokers = el._vei || (el._vei = {});

      //获取为该元素伪造的事件处理函数 invoker
      let invoker = invokers[key];
      // 获取事件名称  如：onClick  => click
      const name = key.slice(2).toLowerCase();

      if (nextValue) {
        if (!invoker) {
          // 第一次绑定事件
          // 为元素创建一个伪造的事件处理函数 invoker
          //vei  vue event invoker
          // 将事件处理函数映射到对应的key下，避免覆盖。
          invoker = el._vei[key] = (e) => {

            // e.timeStamp 事件发生的时间
            // 如果事件发生的时间小于事件处理函数绑定的时间 则直接return
            //--------- 此处是为了屏蔽绑定事件晚于事件发生事件的事件处理函数的 执行-----
            if (e.timeStamp < invoker.attached) return;

            // 当伪造事件执行的时候  会执行真正的事件处理函数 
            // 如果是个数组 则遍历执行  否则 直接执行
            if (Array.isArray(invoker.value)) {
              invoker.value.forEach(fn => fn(e))
            } else {
              invoker.value(e)
            }
          }

          //将真正的事件处理函数赋值给 invoker.value
          invoker.value = nextValue;

          // 记录时间绑定的时间
          invoker.attached = performance.now();

          // 新绑定事件
          el.addEventListener(name, invoker);
        } else {
          // 如果invoker 存在 则表示 更新 事件
          // 只需要更新 invoker.value 即可
          invoker.value = nextValue;
        }
      } else if (invoker) {
        //新事件不存在 且之前绑定的事件存在 则 移除事件
        el.removeEventListener(name, invoker.value);
      }

    } else if (key === 'class') {
      el.className = nextValue || '';
    } else if (shouldSetAsProps(el, key, nextValue)) {
      const type = typeof el[key]
      if (type === 'boolean' && nextValue === '') {
        el[key] = true;
      } else {
        el[key] = nextValue;
      }
    } else {
      el.setAttribute(key, nextValue);
    }
  }
})

renderer.render(vnode, document.querySelector('#app'));